/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <vector>

#include <melon/expected.h>
#include <melon/optional.h>
#include <melon/json/dynamic.h>
#include <melon/json_pointer.h>

namespace melon {
    /*
     * json_patch
     *
     * As described in RFC 6902 "JSON Patch".
     *
     * Implements parsing. Application over data structures must be
     * implemented separately.
     */
    class json_patch {
    public:
        enum class parse_error_code : uint8_t {
            undefined,
            invalid_shape,
            missing_op,
            unknown_op,
            malformed_op,
            missing_path_attr,
            malformed_path_attr,
            missing_from_attr,
            malformed_from_attr,
            missing_value_attr,
            overlapping_pointers,
        };

        /*
         * If parsing JSON patch object fails we return err code along with
         * pointer to part of JSON document that we could not parse
         */
        struct parse_error {
            // one of the above error codes
            parse_error_code error_code{parse_error_code::undefined};
            // pointer to object that caused the error
            Dynamic const *obj{};
        };

        enum class patch_operation_code : uint8_t {
            invalid = 0,
            test,
            remove,
            add,
            replace,
            move,
            copy,
        };

        /*
         * Single JSON patch operation. Argument may vary based on op type
         */
        struct patch_operation {
            patch_operation_code op_code{patch_operation_code::invalid};
            json_pointer path;
            Optional<json_pointer> from;
            Optional<Dynamic> value;

            friend bool operator==(
                patch_operation const &lhs, patch_operation const &rhs) {
                return lhs.op_code == rhs.op_code && lhs.path == rhs.path &&
                       lhs.from == rhs.from && lhs.value == rhs.value;
            }

            friend bool operator!=(
                patch_operation const &lhs, patch_operation const &rhs) {
                return !(lhs == rhs);
            }
        };

        json_patch() = default;

        ~json_patch() = default;

        static Expected<json_patch, parse_error> try_parse(
            Dynamic const &obj) noexcept;

        std::vector<patch_operation> const &ops() const;

        enum class patch_application_error_code : uint8_t {
            other,
            // "from" pointer did not resolve
            from_not_found,
            // "path" pointer did not resolve
            path_not_found,
            // "test" condition failed
            test_failed,
        };

        struct patch_application_error {
            patch_application_error_code error_code{};
            // index of the patch element (in array) that caused error
            size_t index{};
        };

        /*
         * Mutate supplied object in accordance with patch operations. Leaves
         * object in partially modified state if one of the operations fails.
         */
        Expected<Unit, patch_application_error> apply(Dynamic &obj) const;

    private:
        std::vector<patch_operation> ops_;
    };
} // namespace melon
